/****************************************************************************\
* PROJECT: xmega-music-player												 *
* Created By: Alex Arseneau													 *
* File: main.c																 *
*																			 *
* Desc: primary file for music-player. Designed specifically for use with    *
* atxmega256a3b. Compiled using avr-gcc/avr-c++.                             *
 ****************************************************************************/


 /****************************************************************************
* Includes                                                                  *
****************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <avr/pgmspace.h>
#include <string.h>
#include <ctype.h>

extern "C" {
#include "globals.h"
#include "vs1053_driver.h"
#include "fat32.h"
#include "musiclib.h"
};

#include "state_machine.h"
#include "sd_driver.h"

#define STATUS_LED 0x01

#define PLAYBACK_INDICATOR_LED 0x02

#define ENABLE_PLAYBACK_LED() do{PORTF.OUT |= PLAYBACK_INDICATOR_LED;} while(0)

#define DISABLE_PLAYBACK_LED() do{PORTF.OUT = PORTF.OUT & ~PLAYBACK_INDICATOR_LED;} while(0)

#define ENABLE_BUTTONS() do {PMIC.CTRL = (PMIC.CTRL | PMIC_MEDLVLEN_bm); PORTF.OUT |= STATUS_LED;} while(0)

#define DISABLE_BUTTONS() do {PMIC.CTRL = (PMIC.CTRL & (~PMIC_MEDLVLEN_bm)); } while(0)

#define INTERRUPT_PORT PORTE

#define BACK_PIN (1<<0)
#define DOWN_PIN (1<<2)
#define UP_PIN (1<<3)
#define SELECT_PIN (1<<4)

#define VOLUP_PIN (1<<2)
#define VOLDOWN_PIN (1<<0)

#define VOL_PINMASK (VOLUP_PIN | VOLDOWN_PIN)

#define CARD_DETECT_PIN 0x02

#define BUTTON_PINS (BACK_PIN | DOWN_PIN | UP_PIN | SELECT_PIN)

#define CARD_CTRL_bm (PORT_OPC_WIREDANDPULL_gc | PORT_ISC_BOTHEDGES_gc)

#define BUTTON_CTRLMASK_bm (PORT_OPC_WIREDANDPULL_gc | PORT_ISC_FALLING_gc)
/****************************************************************************
* Function Prototypes                                                       *
****************************************************************************/

/****************************************************************************
* Purpose:	Sets Pin States/Configuration
* Input:	No Input
* Output:   No Output
****************************************************************************/
void initIO(void);

/****************************************************************************
* Purpose:	Initializes the state machine global structure
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void initStateMachine(void);

/****************************************************************************
* Purpose:	Initalizes the internal 32MHz clock, and sets it as the system
*			clock
* Input:	No Input
* Output:   No Output
****************************************************************************/
void Config32MHzClock(void);

/****************************************************************************
* Purpose:	Does the next step in the state sequence global variable
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void doStep(void);

/****************************************************************************
* Purpose:	Runs the initial system startup code (system clk, io, etc)
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void doStartUp(void);

/****************************************************************************
* Purpose:	Configures a port's pins with mask
* Input:	Pointer to PORT structure, a pinmask containing the bits
*			corresponding to pins, and the pin configuration mask associated
*			with pins to be configured
* Output:   No Output
****************************************************************************/
void configPortPins( PORT_t * port, uint8_t pinMask, uint8_t ctrlMask);

/****************************************************************************
* Purpose:	Sets up the port IO, and sets the interrupt flags for associated
*			pins
* Input:	No Input
* Output:   No Output
****************************************************************************/
void configInterrupts(void);

/****************************************************************************
* Purpose:	Determines what kind of button press occured, handles it by
*			either passing it along to the MenuLib or by calling the
			necessary functions required
* Input:	No Input
* Output:   No Output
****************************************************************************/
void handleButton(void);

/****************************************************************************
* Purpose:	Determines what kind of media change occured, and sets the
*			state machine accordingly
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void handleMediaChange(void);

/****************************************************************************
* Purpose:	Starts a timeout timer for media insertion change. Acts as a
*			debouncer
* Input:	No Input
* Output:   No Output
****************************************************************************/
void startMediaTimer(void);

/****************************************************************************
* Purpose:	Stops the Media timer and resets it
* Input:	No Input
* Output:   No Output
****************************************************************************/
void stopMediaTimer(void);

/****************************************************************************
* Purpose:	Starts a timeout timer for button presses. Acts as a
*			debouncer
* Input:	No Input
* Output:   No Output
****************************************************************************/
void startButtonTimer(void);

/****************************************************************************
* Purpose:	Stops the button timer and resets it
* Input:	No Input
* Output:   No Output
****************************************************************************/
void stopButtonTimer(void);

/****************************************************************************
* Purpose:	Sets up the fat file system
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   Returns 0 on success, 1 on failure
****************************************************************************/
int initFS();

/****************************************************************************
* Purpose:	Sets the state machine to waiting for mecia insertion state
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void setStateFail();

/****************************************************************************
* Primary State Sequences                                                   *
****************************************************************************/
system_step_t sd_card_inserted[] = {
	START_STEP,
	BEGIN_SD_INIT,
	SETUP_SD_CARD,
	SETUP_FAT_LIB,
	SETUP_DECODER,
	END_STEP
};

system_step_t sd_card_removed[] = {
	START_STEP,
	CANCEL_PLAYBACK,
	DISABLE_FAT_LIB,
	DISABLE_MUSIC_LIB,
	END_STEP
};

system_step_t decoding_audio[] = {
	START_STEP,
	FIND_FIRST_FILE,
	START_PLAYBACK,
	CONTINUE_PLAYBACK,
	PLAYBACK_FINISH,
	GET_NEXT_FILE,

	END_STEP,

	SKIP_SONG,
	PREV_SONG,

	GET_PREV_FILE,

	PAUSE_PLAYBACK,
	PAUSED,
	RESUME_PLAYBACK,

	LAST_STEP
};

/****************************************************************************
* Global Variables                                                          *
****************************************************************************/
SystemStatus_t status; // Our instance of system status flags and state info


/****************************************************************************
* Function: main()                                                          *
* Desc: This is where the program, and the core of the state machine lives  *
****************************************************************************/
int main(void)
{
	// Initialize system
	doStartUp();

	// Enable Global Interrupts
	sei();

	// This is where the state machine lives
    while(1)
    {
		// Handle any media changes
		if (status.media_changed)
			handleMediaChange();

		// Handle any button presses
		if (status.button_pressed)
			handleButton();

		// Only do step if we are not IDLE
		if (status.state != IDLE)
		{
			doStep();

			// Check to see if last step performed resulted in an "end of sequence"
			if (status.sequence_complete != 0)
			{
				// Set state to IDLE
				status.sequence_complete = 0;
				status.sequence = NULL;
				status.state = IDLE;
				status.step = 0;
			}
		}
    }

	// Should never reach here, but for code compliance "return 0"
	return 0;
}

/****************************************************************************
* Purpose:	Runs the initial system startup code (system clk, io, etc)
* Input:	No Input. Requires global var "status" of type SystemStatus_t                           *
* Output:   No Output
****************************************************************************/
void doStartUp()
{
	// Initialize the state machine structure
	initStateMachine();

	// Configure internal osc to run at 32MHz
	Config32MHzClock();

	// Configure debug serial output and stdio stream
	debugInit();

	// Wait a bit...for debug purposes
	for (int i = 0; i < 160; i++)
	{
		_delay_ms(20);
	}

	// Configure system IO pins and external interrupts
	initIO();

	// Setup the mp3 decoder
	vsInit();

	// Configure mp3 decoder
	vsConfigureDevice();

	// Set volume to minimum
	vsSetVol(VOL_MIN);

	// If the sd card is present at system startup, set system state machine accordingly
	if (PORTE.IN & CARD_DETECT_PIN)
	{
		status.media_state = MEDIA_CONNECTED;
		status.sequence = sd_card_inserted;
		status.state = MEDIA_INSERTED;
		sdEnableSpi();
	}

	else
	{
		status.media_state = NO_MEDIA;
	}

	// Setup the multi-level interrupts to allow high level interrupts (card detect changes)
	PMIC.CTRL = ((PMIC.CTRL & ~(PMIC_HILVLEN_bm)) | PMIC_HILVLEN_bm );
}

/****************************************************************************
* Purpose:	Sets up the port IO, and sets the interrupt flags for associated
*			pins
* Input:	No Input
* Output:   No Output
****************************************************************************/
void configInterrupts()
{
	PORTE.DIR = 0x00;

	// Setup Buttons pins on porte
	configPortPins(&PORTE,BUTTON_PINS,BUTTON_CTRLMASK_bm);

	// Setup Buttons pins on porta
	configPortPins(&PORTA,VOL_PINMASK,BUTTON_CTRLMASK_bm);

	// Setup SD card pins
	configPortPins(&PORTE,CARD_DETECT_PIN,CARD_CTRL_bm);

	//Enable interrupts at the port level
	PORTE.INTCTRL = (PORTE.INTCTRL & (PORT_INT0LVL_MED_gc | PORT_INT1LVL_HI_gc)) | PORT_INT0LVL_MED_gc | PORT_INT1LVL_HI_gc;
	PORTA.INTCTRL = (PORTA.INTCTRL &  (PORT_INT0LVL_MED_gc)) |  PORT_INT0LVL_MED_gc;
	// Assign Interrupt vector to each class (button, card)

	PORTE.INT0MASK = BUTTON_PINS;
	PORTA.INT0MASK = VOL_PINMASK;
	PORTE.INT1MASK = CARD_DETECT_PIN;

	PMIC.CTRL |=  PMIC_HILVLEN_bm;
}

/****************************************************************************
* Purpose:	Configures a port's pins with mask
* Input:	Pointer to PORT structure, a pinmask containing the bits
*			corresponding to pins, and the pin configuration mask associated
*			with pins to be configured
* Output:   No Output
****************************************************************************/
void configPortPins( PORT_t * port, uint8_t pinMask, uint8_t ctrlMask)
{
	// Allows for configuration of all pins in pinMask in one write, instead of one per pin
	PORTCFG.MPCMASK = pinMask;

	port->PIN0CTRL = ctrlMask;
}

/****************************************************************************
* Purpose:	Sets Pin States/Configuration
* Input:	No Input
* Output:   No Output
****************************************************************************/
void initIO(void)
{
	sdPORT.DIR = 0x00;

	PORTF.DIR |= STATUS_LED | PLAYBACK_INDICATOR_LED;

	PORTF.OUT = (PORTF.OUTCLR & ( ~(STATUS_LED | PLAYBACK_INDICATOR_LED) ));

	configInterrupts();
}

/****************************************************************************
* Purpose:	Sets up the fat file system
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   Returns 1 on failure, 0 on success
****************************************************************************/
int initFS()
{
	// If initalization of the fat library fails then return 1
	if (fatfs_init(mediaRead) != INIT_SUCCESS)
	{
		// Disable media present LED
		PORTF.OUT = PORTF.OUT & (~STATUS_LED);
		return 1;
	}

	// Ensure that the playback LED is off
	DISABLE_PLAYBACK_LED();

	// Setup Music Library, if it fails return 1 (failure)
	if ( !initMusicLib() )
		return 1;

	return 0;

}

/****************************************************************************
* Purpose:	Initializes the state machine global structure
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void initStateMachine()
{
	status.button_pressed = 0;
	status.button_to_handle = 0;
	status.media_changed = 0;
	status.media_state = NO_MEDIA;

	status.sequence = NULL;
	status.sequence_complete = false;
	status.state = IDLE;

	sdDisableSpi();

	status.step = 0;

	status.volume = VOL_DEFAULT;
}

/****************************************************************************
* Purpose:	Sets the state machine to waiting for mecia insertion state
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void setStateFail()
{
	// Disable buttons and spi interface
	DISABLE_BUTTONS();
	sdDisableSpi();
	fatfs_disable();
	disableMusicLib();
	status.media_changed = true;
	status.media_state = NO_MEDIA;
}

/****************************************************************************
* Purpose:	Determines what kind of media change occured, and sets the
*			state machine accordingly
* Input:	No Input. Requires global var "status" of type SystemStatus_t
* Output:   No Output
****************************************************************************/
void handleMediaChange(void)
{
	// If a new card has been detected
	if (status.media_state == MEDIA_CONNECTED)
	{
		printf("Media Connected\r\n");

		// Setup the state machine to advance to next state
		status.sequence = sd_card_inserted;
		status.state = MEDIA_INSERTED;
		status.step = 0;
		status.increment = false;

		// Enable spi interface
		sdEnableSpi();
	}

	else if (status.media_state == NO_MEDIA)
	{
		// Disable the spi interface
		sdDisableSpi();

		// Make sure that buttons are disabled
		DISABLE_BUTTONS();

		// Set the state machine to advance to next state
		status.sequence = sd_card_removed;
		status.state = MEDIA_REMOVED;
		status.step = 0;
		status.increment = false;
		status.media_state = NO_MEDIA;

		// Disable the media present LED

		PORTF.OUT = PORTF.OUT & (~STATUS_LED);
		printf("Media Disconnected!\r\n");
	}
	status.sequence_complete = false;
	status.media_changed = 0;
	//Clear the media changed flag

}

/****************************************************************************
* Purpose:	Determines what kind of button press occured, handles it by
*			either passing it along to the MenuLib or by calling the
			necessary functions required
* Input:	No Input
* Output:   No Output
****************************************************************************/
void handleButton(void)
{
	int i;

	ButtonAction_t action;

	// If buttons fired even though we should have already turned them off
	if (status.media_state != MEDIA_CONNECTED)
	{
		// If we failed to turn off buttons somehow, do it now.
		status.button_pressed = 0;
		status.button_to_handle = 0;
		DISABLE_BUTTONS();

		return;
	}

	// Pass the button on to the menusystem
	// This could be simplified by changing the pinmasks to match the input types
	if (status.update_volume == true)
	{
		status.update_volume = false;
		if (status.button_pressed == VOLUP_PIN)
		{
			action = INCR_VOL;
		}
		else if (status.button_pressed == VOLDOWN_PIN)
		{
			action = DECR_VOL;
		}
		else
			action = NO_ACTION;
	}
	else
	{
		if (status.button_pressed == BACK_PIN)
			action = navigate(LIB_BACK);
		else if (status.button_pressed == DOWN_PIN)
			action = navigate(LIB_DOWN);
		else if (status.button_pressed == UP_PIN)
			action = navigate(LIB_UP);
		else if (status.button_pressed == SELECT_PIN)
			action = navigate(LIB_SELECT);
		else
			action = NO_ACTION;
	}

	// Perform required action based on output of menu system
	switch (action)
	{
		case NO_ACTION:
			break;

		case NEW_SONG_SELECTED:
			// If the state is currently idle, the begin playback
			if (status.state == IDLE)
			{
				status.state = DECODING_MP3;
				status.sequence = decoding_audio;
				status.step = 0;
			}

			// Otherwise this we are currently decoding or are pause, so move to new file
			else
			{
				for(i = 0; decoding_audio[i] != FIND_FIRST_FILE; i++);
				status.step = i;
			}

			break;

		case SONG_NEXT:
			// Only skip to next file iff we are currently decoding audio
			if (status.state == DECODING_MP3)
			{
				printf("Skip next\r\n");
				for(i = 0; decoding_audio[i] != SKIP_SONG; i++);

				status.step = i;
			}
			break;

		case SONG_PREV:
			// If we are currently decoding skip back one normally
			if (status.state == DECODING_MP3)
			{
				printf("Skip back\r\n");
				for(i = 0; decoding_audio[i] != PREV_SONG; i++);
				status.step = i;
			}

			// otherwise we need to restart playback at the song we are now pointing to via menu system
			else
			{
				printf("Skip back\r\n");
				status.state = DECODING_MP3;
				status.sequence = decoding_audio;
				for(i = 0; decoding_audio[i] != FIND_FIRST_FILE; i++);
				status.step = i;
				status.increment = false;
			}
			break;

		case PAUSE_PLAY:
			// If currently decoding and not already trying to pause audio, set to pause mode
			if (status.sequence == decoding_audio && (status.sequence[status.step] != PAUSED && status.sequence[status.step] != PAUSE_PLAYBACK))
			{
				for (i = 0;decoding_audio[i] != PAUSE_PLAYBACK;i++);
				status.step = i;
				DISABLE_PLAYBACK_LED();
			}
			// If we are currently paused or not decoding
			else if (status.state == IDLE || status.sequence[status.step] == PAUSED)
			{
				ENABLE_PLAYBACK_LED();

				// If not decoding, restart decoding at song pointed to menu system
				if (status.sequence == NULL)
				{
					for(i = 0; decoding_audio[i] != FIND_FIRST_FILE; i++);
					status.sequence = decoding_audio;
					status.state = DECODING_MP3;
					status.step = i;
				}
				// If just paused then resume playback
				else if (status.sequence[status.step] == PAUSED)
				{
					for(i=0; decoding_audio[i]!= CONTINUE_PLAYBACK;i++);
					status.step = i;
				}
			}

			break;

		case INCR_VOL:
			if (status.volume > VOL_CHANGE_VAL)
			{
				status.volume -= VOL_CHANGE_VAL;
				vsSetVol(status.volume);
			}
			break;
		case DECR_VOL:
			if (status.volume < (VOL_MIN - VOL_CHANGE_VAL))
			{
				status.volume += VOL_CHANGE_VAL;
				vsSetVol(status.volume);
			}
			break;

	}

	// Clear flags
	status.increment = 0;
	status.button_pressed = status.button_to_handle = 0;
}

/***************************************************************************
* Function: doStep()                                                       *
* Desc: Determines which of the major states we are in, calls function     *
*		which knows how to execute the next step                           *
* REQS: Global var "stat" of type SystemStatus_t                           *
***************************************************************************/
void doStep(void)
{
	uint16_t i;
	uint16_t j;

	// If in IDLE state or an empty seqyence, we should not be here!
	if  (status.state == IDLE || status.sequence == NULL)
		return;

	// Perform step referenced by sequence array at step number
	switch (status.sequence[status.step])
	{
		case START_STEP:
		// Every sequence begins at the start
		status.increment = true;
			break;

		// SD Card inserted sequence
		case BEGIN_SD_INIT:
			// Does nothing anymore other than print debug info
			status.increment = true;
			break;

		case SETUP_SD_CARD:
			// Attempt media initialization, if it fails set fail state
			if (mediaInit() != 0)
			{
				setStateFail();
			}
			// Otherwise move to next step
			status.increment = true;
			break;

		case SETUP_FAT_LIB:
			// Attempt to initalize fat library. If it fails, set fail state
			if (initFS() != 0)
			{
				setStateFail();
			}
			// Otherwise we are now free to move about the menu system
			else
			{
				ENABLE_BUTTONS();
				status.increment = true;
				status.sequence_complete = true;
			}
			break;

		// SD Card removed sequence
		case CANCEL_PLAYBACK:
			DISABLE_BUTTONS();

			// Stops media playback either after song finish/change
			vsStopPlayBack();

			status.increment = true;

			break;

		case DISABLE_FAT_LIB:
			// Closes any open files (only 1)
			fat_fclose(status.fp);

			// Disable (just in case we somehow get stuck in a read operation
			fatfs_disable();

			status.increment = true;

			break;

		case DISABLE_MUSIC_LIB:

			disableMusicLib();

			status.increment = true;

			status.sequence_complete = true;

			break;

		// DECODING audio sequence
		case FIND_FIRST_FILE:
			// Attempt to get mp3 file pointed to by menu system
			status.fp = getSelection();

			// Provided file is valid begin playback
			if (status.fp != NULL)
			{
				// Set volume of mp3 decoder
				vsSetVol(status.volume);

				status.increment = true;
			}

			// Otherwise stop trying to playback
			else
			{
				printf("File Not Found\r\n");
				status.sequence_complete = true;
			}
			break;

		case START_PLAYBACK:
			// Provided file is valid do playback
			if (!status.fp)
			{
				 status.sequence_complete = true;
			}

			else
			{
				status.increment = true;
			}

			break;

		case CONTINUE_PLAYBACK:
			// Buffers next segment of data (upt to 256 bytes) and sends
			// it to mp3 decoder while file is still valid
			if (status.fp->flags != FAT_FILE_GOOD)
			{
				// Song has finished, move to next step
				status.increment = true;
			}
			else
			{
				// Buffer next 256 bytes
				i = fat_fread(status.buf, 256,status.fp);

				//transmit up to i bytes to decoders
				for (j = 0; j < i; j++)
				{
					vsWriteData((uint8_t*)status.buf+j);
				}

				// Ensure we stay at current step
				status.increment = 0;
			}
			break;

		case PLAYBACK_FINISH:
			// Close the file
			fat_fclose(status.fp);
			// Stop playback
			vsStopPlayBack();

			status.increment = true;

			break;

		case GET_NEXT_FILE:
			// Load next song from menu system
			status.fp = getNextSong();

			// If file is invalid we are at end of sequence
			if (status.fp == NULL)
			{
				status.sequence_complete = true;
				status.increment = false;
			}
			// Otherwise make sure display is valid, and start file playback
			else
			{
				updateDisplay();
				status.increment = false;

				for (i=0;decoding_audio[i] != START_PLAYBACK;i++);

				status.step = i;
			}
			break;

		case GET_PREV_FILE:
			// Load previous song
			status.fp = getPrevSong();

			// If file is invalid we are at end of sequence
			if (status.fp == NULL)
			{
				status.sequence_complete = true;
				status.increment = false;
			}

			// Otherwise make sure display is valid and start file playback
			else
			{
				updateDisplay();
				status.increment = false;

				for (i=0;decoding_audio[i] != START_PLAYBACK;i++);

				status.step = i;
			}
			break;

		case PAUSE_PLAYBACK:
			// Send stop playback to vs1053
			vsStopPlayBack();

			status.increment = true;

			break;

		case PAUSED:
			// Wait in this state until a user input or media change forces state change
			status.increment = false;

			break;

		case RESUME_PLAYBACK:
			// To resart playback
			int i;
			// Make sure volume is enabled
			vsSetVol(status.volume);

			// find index into sequence for CONTINUE_PLAYBACK
			for (i = 0;status.sequence[i] != CONTINUE_PLAYBACK;i++);

			status.increment = false;

			status.step = i;

			break;

		case SKIP_SONG:
			// To skip song find index in decoding audio corrisponding to get_next_file
			for (i = 0; decoding_audio[i] != GET_NEXT_FILE; i++);

			status.increment = false;

			// Close the current file
			fat_fclose(status.fp);

			// Stop playback
			vsStopPlayBack();

			status.fp = NULL;

			// Move to sequence step i
			status.step = i;

			break;

		case PREV_SONG:
			// To skip song find index in decoding audio corrisponding to get_prev_file
			for (i = 0; decoding_audio[i] != GET_PREV_FILE; i++);

			status.increment = false;

			// Close the current file
			fat_fclose(status.fp);

			// Stop playback
			vsStopPlayBack();

			status.fp = NULL;

			// Move to sequence step i
			status.step = i;

			break;

		case END_STEP:
			// If we reach the end step the set sequence_complete to true
			status.sequence_complete = true;
			break;

		default:
			// Should never get here. If we do, set state to IDLE and seq to NULL
			status.sequence = NULL;
			status.state = IDLE;
			break;
	}

	// Only increment step when we say we should
	if (status.increment)
	{
		status.step++;
		status.increment = 0;
	}
}

/****************************************************************************
* Purpose:	Starts a timeout timer for button presses. Acts as a
*			debouncer
* Input:	No Input
* Output:   No Output
****************************************************************************/
void startButtonTimer(void)
{
	TCC1.CNT = 0x00;
	TCC1.PER = 31250/4;
	TCC1.CTRLA = 0x07; // Prescaler SYSCLK/1024 = 31.250 KHz
	TCC1.CTRLB = 0x00;
	TCC1.INTCTRLA = 0x03; // Enable timer overflow interrupt at High Level interrupt
}

/****************************************************************************
* Purpose:	Stops the button timer and resets it
* Input:	No Input
* Output:   No Output
****************************************************************************/
void stopButtonTimer(void)
{
	TCC1.CTRLA = 0x00; // Timer/Counter turn off
	TCC1.INTCTRLA = 0x00; //Disable timer interrupts
	TCC1.INTFLAGS |= 0x03; // Clear the interrupt flags so we don't handle this (just in case)
}

/****************************************************************************
* Purpose:	Starts a timeout timer for media insertion change. Acts as a
*			debouncer
* Input:	No Input
* Output:   No Output
****************************************************************************/
void startSDConnectTimer(void)
{
	//Set a 1 second timer
	TCC0.CNT = 0x00;
	TCC0.PER = 31250; //
	TCC0.CTRLA = 0x07; // Prescaler SYSCLK/1024 = 31.250 KHz
	TCC0.CTRLB = 0x00;
	TCC0.INTCTRLA = 0x03; // Enable timer overflow interrupt at High Level interrupt
}

/****************************************************************************
* Purpose:	Stops the Media timer and resets it
* Input:	No Input
* Output:   No Output
****************************************************************************/
void stopSDCardConnectTimer(void)
{
	TCC0.CTRLA = 0x00; // Timer/Counter turn off
	TCC0.INTCTRLA = 0x00; //Disable timer interrupts
	TCC0.INTFLAGS |= 0x03; // Clear the interrupt flags so we don't handle this (just in case)
}

/****************************************************************************
* Purpose:	Initalizes the internal 32MHz clock, and sets it as the system
*			clock
* Input:	No Input
* Output:   No Output
****************************************************************************/
void Config32MHzClock(void)
{
	//Security Signature to modify clock
	CCP = CCP_IOREG_gc;

	// enable internal 32MHz oscillator
	OSC.CTRL = OSC_RC32MEN_bm;

	// wait for oscillator ready
	while(!(OSC.STATUS & OSC_RC32MRDY_bm));

	//Security Signature to modify clock
	CCP = CCP_IOREG_gc;

	//select sysclock 32MHz osc
	CLK.CTRL = 0x01;

	// Set the system clock divider to zero division
	CLK.PSCTRL = 0x00;
}

/****************************************************************************
* Purpose:	Menu Button interrupt Routine. Servies button presses and starts
*			button debounce timer, as well as notifies state machine that a
*			button has been pressed and which button that is
* Input:	No Input
* Output:   No Output
****************************************************************************/
ISR(PORTE_INT0_vect, ISR_BLOCK)
{
	// Get rid of any bits not associated with buttons
	status.button_pressed = (BUTTON_PINS & (~PORTE.IN));

	// Make sure we are already waiting to handle a button press
	if (!status.button_to_handle)
	{
		// Only allow a single button to be pressed, ignore otherwise...nice try andy
		if ((status.button_pressed != BACK_PIN) && (status.button_pressed != DOWN_PIN) && (status.button_pressed != UP_PIN) && (status.button_pressed != SELECT_PIN))
		{
			status.button_pressed = 0;
			PORTE.INTFLAGS |= 0x01;
			return;
		}

		// Otherwise this is a valid press. Disable further button presses until timer
		// overflows
		else
		{
			DISABLE_BUTTONS();

			status.button_to_handle = 1;

			startButtonTimer();
		}
	}

	PORTE.INTFLAGS |= 0x01;
};

/****************************************************************************
* Purpose:	Volume Button interrupt Routine. Services volume button presses
*			and starts button debounce timer, as well as notifies state
*			machine that a button has been pressed and which button that is
* Input:	No Input
* Output:   No Output
****************************************************************************/
ISR(PORTA_INT0_vect, ISR_BLOCK)
{
	status.button_pressed = (VOL_PINMASK & (~PORTA.IN));

	if (!status.button_to_handle)
	{
		if ((status.button_pressed != VOLUP_PIN) && (status.button_pressed != VOLDOWN_PIN))
		{
			status.button_pressed = 0;
			status.update_volume = 0;
			PORTA.INTFLAGS |= 0x01;
			return;
		}

		else {

			DISABLE_BUTTONS();
			status.update_volume = true;
			status.button_to_handle = 1;
			startButtonTimer();
		}
	}
	PORTA.INTFLAGS |= 0x01;
};

/****************************************************************************
* Purpose:	Timer0 overflow interrupt. Fires after sd card has been connected
*			for 1 second. Helps debounce messy sd card insertions
* Input:	No Input
* Output:   No Output
****************************************************************************/
ISR(TCC0_OVF_vect, ISR_BLOCK)
{
	// Disable the timer and clear it
	stopSDCardConnectTimer();

	// Make sure spi is enabled
	sdEnableSpi();

	// Notify state machine of change
	status.media_changed = true;
	status.media_state = MEDIA_CONNECTED;
	TCC0.INTFLAGS |= 0x03; // Manually clear interrupt flags
};

/****************************************************************************
* Purpose:	Timer1 overflow interrupt. Fires after 1/2 second has passed
*			since last button was pressed. Re-enables button presses
* Input:	No Input
* Output:   No Output
****************************************************************************/
ISR(TCC1_OVF_vect, ISR_BLOCK)
{
	// Reset the timer
	stopButtonTimer();

	TCC1.INTFLAGS |= 0x03; // Manually clear interrupt flags

	PORTE.INT0MASK |= 0x01;//Clear any pending button interrupts

	ENABLE_BUTTONS();
};

/****************************************************************************
* Purpose:	Async Pin2 PORTE SD Card detect interrupt. Fires on rising &
*			falling edges (on connect and disconnect). Services changes
*			accordingly
* Input:	No Input
* Output:   No Output
****************************************************************************/
ISR(PORTE_INT1_vect, ISR_BLOCK)
{
	volatile uint8_t temp_val;

	temp_val = PORTE.IN;

	// If this is a connection based interrupt
	if ((temp_val & CARD_DETECT_PIN) != 0)
		startSDConnectTimer();

	// Otherwise this is a disconnect based interrupt
	else
	{
		// If we were debouncing card detect, stop it, not a solid connection
		if ((TCC0.INTCTRLA & 0x03) != 0)
			stopSDCardConnectTimer();

		// Otherwise sd card has been removed
		else
		{
			status.media_changed = true;
			status.media_state = NO_MEDIA;
			// Release any spi transfers in progress
			sdDisableSpi();
			// Turn off buttons
			DISABLE_BUTTONS();
			// Release any fat filesystem transfers/while loops
			fatfs_disable();
		}
	}
	PORTE.INTFLAGS |= 0x02;
};

/****************************************************************************
* Purpose:	Handles any otherwise unhandled interrupts. Without this if a
*			unhandled interrupt fires, the system reboots
* Input:	No Input
* Output:   No Output
****************************************************************************/
ISR(BADISR_vect, ISR_BLOCK)
{
};